<style lang="less">
.uploader{
    .el-upload{
        text-align: inherit;
    }

    .el-upload__tip{
        margin-top: 0;
    }

    .el-upload-list--text{
        .el-upload-list__item{
            position: relative;

            .el-upload-list__item-name i{
                margin-right: 10px;
            }

            .el-icon-close,
            .el-upload-list__item-status-label{
                background: #FFF;
                border-radius: 50%;
                line-height: 25px;
                height: 25px;
                width: 25px;
                text-align: center;

                opacity: 1;
                right: auto;
                left: 0;
                top: 0;
            }
        }
    }

    .el-upload-list--picture-card{
        .el-upload-list__item{
            text-align: center;

            &:before{
                display: inline-block;
                content: "";
                height: 100%;
                vertical-align: middle;
            }

            .el-upload-list__item-thumbnail{
                width: auto;
                height: auto;
                max-width: 100%;
                max-height: 100%;
                vertical-align: middle;
            }
        }
    }

    .el-upload--picture-card{
        box-sizing: border-box;
        background-color: #fbfdff;
        border: 1px dashed #c0ccda;
        border-radius: 6px;
        color: #999;
        cursor: pointer;
        line-height: inherit;
        padding: 50px 10px 0;
        width: 148px;
        height: 148px;
        text-align: center;
        vertical-align: top;

        &:hover{
            border-color: #20a0ff;
            color: #20a0ff;
        }

        i{
            font-size: 28px;
            vertical-align: top;
        }

        .tips{
            color: #999;
            line-height: 1.5;
            margin-top: 10px;
        }
    }
}
</style>
<template>
    <ELUpload
        ref="uploader"
        class="uploader"
        :class="{
            'uploader-model': hasModel()
        }"

        :name="name"
        :data="data"
        :drag="drag"
        :accept="accept"
        :action="action"
        :headers="headers"
        :multiple="multiple"
        :disabled="disabled"
        :auto-upload="autoUpload"
        :show-file-list="showFileList"
        :with-credentials="withCredentials"
        :file-list="fileList"
        :list-type="listType"

        :http-request="httpRequest"
        :before-upload="beforeUpload"
        :on-success="onSuccess"
        :on-error="onError"
        :on-remove="onRemove"
        :on-preview="onPreview"
    >
        <slot name="trigger"></slot>
        <slot></slot>
        <slot name="tip"></slot>
    </ELUpload>
</template>
<script>
import Promise from 'bluebird';
import ELUpload from 'element-ui/lib/upload';

import APP from '../common/ttxs';
import qiniu from '../services/qiniu';

const DEFAULT_URL_KEY = 'url';

// 简单处理扩展映射
const extToTypeMap = {};
['webp', 'jpeg', 'jpg', 'png', 'gif'].forEach(k => {
    extToTypeMap[k] = 'image';
});
['mp4', 'mov', 'avi', 'mkv'].forEach(k => {
    extToTypeMap[k] = 'video';
});
['mp3', 'm4r'].forEach(k => {
    extToTypeMap[k] = 'audio';
});

const fetchImageSize = function(url) {
    return new Promise((resolve, reject) => {
        const img = new Image();

        Object.assign(img, {
            onload() {
                resolve({
                    height: img.height,
                    width: img.width
                });
            },
            onerror() {
                reject(new Error('Image load error'));
            }
        });

        img.src = url;
    });
};

const fetchVideoSize = function(url) {
    return new Promise((resolve, reject) => {
        const video = document.createElement('video');

        Object.assign(video, {
            onloadedmetadata() {
                resolve({
                    duration: Math.round(1000 * video.duration) || 0,
                    height: video.videoHeight,
                    width: video.videoWidth
                });
            },
            onerror() {
                reject(new Error('Video load error'));
            }
        });

        video.src = url;
    })
    .timeout(9000, 'Video load metadata timeout');
};

const methods = {...ELUpload.methods};
Object.keys(methods).forEach(k => {
    const handler = methods[k];

    methods[k] = function(...args) {
        const uploader = this.$refs.uploader;

        if(uploader) {
            handler.apply(uploader, args);
        }
    };
});

export default {
    components: {
        ELUpload: ELUpload
    },
    data() {
        return {
            uploadedFiles: []
        };
    },
    props: {
        ...ELUpload.props,

        autoFillFileList: {
            type: Boolean,
            default: true
        },
        autoFillType: {
            type: Boolean,
            default: true
        },
        autoFillSize: {
            type: Boolean,
            default: true
        },
        fileProps: {
            type: Array,
            default() {
                return [
                    'type', 'url', 'width', 'height',
                    'image', 'video',
                    'duration',
                    'extends'
                ];
            }
        },
        modelUrlKey: {
            type: String,
            default: 'url'
        },
        value: {
            type: Object,
            default: null
        },

        fileList: {
            type: Array,
            default() {
                return [];
            }
        },

        // 判断图片宽高是否符合要求
        width: {
            type: Number,
            default: 0
        },
        height: {
            type: Number,
            default: 0
        },

        accept: {
            type: String,
            default: 'image/jpeg,image/png,image/gif'
        },
        action: {
            type: String,
            default: ''
        },

        httpRequest: {
            type: Function,
            default(options) {
                const {file} = options;

                return qiniu.upload(file, null, options);
            }
        },
        processResult: {
            type: Function,
            default(res) {
                return res;
            }
        },
        // 上传前判断图片尺寸是否符合要求 by pingzi
        beforeUpload: {
            type: Function,
            default(file) {
                const vm = this.$parent.$parent;
                if(/image/.test(file.type)) {
                    if(vm.width || vm.height) {
                        return new Promise((resolve, reject) => {
                            const reader = new FileReader();

                            reader.onload = () => {
                                resolve(fetchImageSize(reader.result));
                            };

                            reader.onerror = () => {
                                reject(new Error('读取图片失败'));
                            };

                            reader.readAsDataURL(file);
                        })
                        .then(res => {
                            const width = res.width;
                            const height = res.height;

                            const dWidth = vm.width;
                            const dHeight = vm.height;

                            const isAllowWidth = dWidth > 0 ? (width === dWidth) : true;
                            const isAllowHeight = dHeight > 0 ? (height === dHeight) : true;

                            return isAllowWidth && isAllowHeight;
                        })
                        .then(res => {
                            return new Promise((resolve, reject) => {
                                if(!res) {
                                    vm.$message.error('上传的图片尺寸不符合要求！');
                                    reject();
                                }

                                resolve();
                            });
                        });
                    }
                }
            }
        },
        onSuccess: {
            type: Function,
            default(res, file, fileList) {
                const vm = this.$parent;
                const modelUrlKey = vm.modelUrlKey;

                return Promise.resolve(res)
                .then(res => {
                    if(res && res.key) {
                        res.url = qiniu.normalizeUrl(res.key);
                    }

                    return res;
                })
                .then(res => {
                    const url = res[modelUrlKey] || res.url;
                    const file = vm.value || res;

                    if(!vm.value && modelUrlKey !== DEFAULT_URL_KEY) {
                        delete file[DEFAULT_URL_KEY];
                    }

                    vm.$set(file, modelUrlKey, url);

                    vm.fixFileModel(file);

                    return file;
                })
                .tap(file => {
                    if(!vm.autoFillType) {
                        return;
                    }

                    const url = file[modelUrlKey];
                    const ext = /\.(\w+)$/.test(url)
                        ? RegExp.$1.toLowerCase()
                        : '';

                    file.type = extToTypeMap[ext] || ext;
                })
                .tap(file => {
                    if(!vm.autoFillSize) {
                        return;
                    }

                    return Promise.try(() => {
                        const url = file[modelUrlKey];
                        const type = file.type;

                        if(type === 'image') {
                            return fetchImageSize(url);
                        }
                        else if(type === 'video') {
                            return fetchVideoSize(url);
                        }
                    })
                    .then(size => {
                        Object.assign(file, size);
                    });
                })
                .then(result => {
                    return Promise.try(() => {
                        return vm.processResult(result, file, fileList);
                    })
                    .then(res => {
                        return res || result;
                    });
                })
                .tap(result => {
                    return vm.onUpload(result, file, fileList);
                })
                .then(result => {
                    if(vm.hasModel()) {
                        vm.updateModel(result);

                        return;
                    }

                    const uploader = vm.$refs.uploader;
                    const uploadFiles = uploader ? uploader.uploadFiles : [];
                    const uploadingFiles = uploadFiles.filter(file => {
                        const status = file.status;

                        return status === 'ready' || status === 'uploading';
                    });

                    vm.uploadedFiles.push(result);

                    if(!uploadingFiles.length) {
                        vm.onComplete();

                        vm.uploadedFiles = [];
                    }
                });
            }
        },
        onUpload: {
            type: Function,
            default(res) {
                return res;
            }
        },
        onComplete: {
            type: Function,
            default() {
                if(this.autoFillFileList) {
                    const uploadedFiles = this.uploadedFiles;

                    this.fileList.push(...uploadedFiles);
                }
            }
        },
        onError: {
            type: Function,
            default(err) {
                APP.showError(err);
            }
        },

        onPreview: {
            type: Function,
            default(file) {
                const vm = this.$parent;
                const data = file.response || file;
                const key = vm.modelUrlKey || vm.$parent.modelUrlKey;
                const url = data[key] || data.url;

                if(url) {
                    window.open(url, '_blank');
                }
            }
        },
        onRemove: {
            type: Function,
            default(file) {
                const vm = this.$parent;

                if(vm.hasModel()) {
                    vm.updateModel(null);

                    return;
                }

                const fileList = vm.fileList;

                if(vm.autoFillFileList && fileList.length) {
                    for(let i = fileList.length - 1; i >= 0; --i) {
                        if(file === fileList[i]) {
                            fileList.splice(i, 1);
                        }
                    }
                }
            }
        }
    },
    methods: {
        ...methods,

        hasModel() {
            return ('value' in this.$options.propsData);
        },
        syncModel() {
            if(!this.hasModel()) {
                return;
            }

            this.clearFiles();
            this.fileList.length = 0;

            const model = this.value;
            if(model) {
                this.fixFileModel(model);

                this.fileList.push(model);
            }
        },
        updateModel(data) {
            const fileList = this.fileList;

            // clean
            for(let i = fileList.length - 1; i >= 0; --i) {
                if(data !== fileList[i]) {
                    fileList.splice(i, 1);
                }
            }

            // update file list
            if(this.$refs.uploader) {
                this.$refs.uploader.uploadFiles = [...fileList];
            }

            this.$emit('input', data || null);
        },

        fixFileModel(file) {
            const defaultKey = DEFAULT_URL_KEY;
            const modelUrlKey = this.modelUrlKey;
            const filePropsMap = this.fileProps.reduce((map, k) => {
                map[k] = true;

                return map;
            }, {});

            const _toJSON = function() {
                return Object.keys(this).reduce((ret, k) => {
                    if(filePropsMap[k]) {
                        ret[k] = this[k];
                    }

                    return ret;
                }, {});
            };

            // Shim toJSON
            Object.defineProperty(file, 'toJSON', {
                configurable: true,
                enumerable: false,
                get() {
                    return _toJSON;
                }
            });

            // Add url getter
            if(
                modelUrlKey !== defaultKey &&
                !Object.getOwnPropertyDescriptor(file, defaultKey)
            ) {
                Object.defineProperty(file, defaultKey, {
                    configurable: true,
                    enumerable: false,
                    get() {
                        return this[modelUrlKey];
                    },
                    set(val) {
                        this[modelUrlKey] = val;
                    }
                });
            }

            return file;
        }
    },
    watch: {
        value: {
            immediate: true,
            handler() {
                this.syncModel();
            }
        },
        fileList: {
            immediate: true,
            handler(fileList) {
                fileList.forEach(file => {
                    this.fixFileModel(file);
                });
            }
        }
    },
    created() {
        // window.x = this;

        this.syncModel();

        // fix accept
        this.$nextTick(() => {
            const input = this.$el.querySelector('input[type=file]');
            if(input) {
                input.accept = this.accept;
            }
        });
    }
};
</script>
